package com.example.demo.study.lambda;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
 * java.util.Stream 表示能应用在一组元素上一次执行的操作序列。
 *     Stream 操作分为中间操作或者最终操作两种，最终操作返回一特定类型的计算结果，
 *     而中间操作返回Stream本身，这样你就可以将多个操作依次串起来(链式编程)。
 *     Stream 的创建需要指定一个数据源，比如 java.util.Collection的子类，List或者Set， Map不支持。
 *     Stream的操作可以串行执行或者并行执行。
 *     Stream 作为 Java 8 的一大亮点，它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念。
 *     Java 8 中的 Stream 是对集合（Collection）对象功能的增强，它专注于对集合对象进行各种非常便利、
 *     高效的聚合操作（aggregate operation），或者大批量数据操作 (bulk data operation)。
 *     Stream API 借助于同样新出现的Lambda表达式，极大的提高编程效率和程序可读性。
 *     同时它提供串行和并行两种模式进行汇聚操作
 */
public class LambdaStream {

    public static void main(String[] args) throws FileNotFoundException {
        //------Stream的构建------
        // 1.使用值构建
        Stream<String> stream = Stream.of("a", "b", "c");
        // 2. 使用数组构建
        String[] strArray = new String[] {"a", "b", "c"};
        stream = Stream.of(strArray);
        stream = Arrays.stream(strArray);
        // 3. 利用集合构建(不支持Map集合)
        List<String> list = Arrays.asList(strArray);
        stream = list.stream();

        //数值Stream的构建
        IntStream stream1 = IntStream.of(new int[]{1, 2, 3});
        //[1,3)
        IntStream stream2 = IntStream.range(1, 3);
        //[1,3]
        IntStream stream3 = IntStream.rangeClosed(1, 3);


        //Stream转换为其它类型:
        Stream<String> stream4 = Stream.of("hello","world","tom");
        //转换为Array
//        String[] strArray1 = stream4.toArray(String[]::new);

        //转换为Collection
//        List<String> list1 = stream4.collect(Collectors.toList());
//        List<String> list2 = stream4.collect(Collectors.toCollection(ArrayList::new));
//        Set<String> set3 = stream4.collect(Collectors.toSet());
//        Set<String> set4 = stream4.collect(Collectors.toCollection(HashSet::new));

        //转换为String
        String str = stream4.collect(Collectors.joining()).toString();
        System.out.println(str);

        //特别注意 : 一个 Stream 只可以使用一次，上面的代码为了简洁而重复使用了多次。
        //这个代码直接运行会抛出异常的:
        //java.lang.IllegalStateException: stream has already been operated upon or closed

        /**
         * 当把一个数据结构包装成Stream后，就要开始对里面的元素进行各类操作了。常见的操作可以归类如下。
         *  Intermediate：中间操作
         *  map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered
         *
         *  Terminal： 最终操作
         *  forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、findFirst、 findAny、 iterator
         *
         *  Short-circuiting： 短路操作
         *  anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit
         *
         *  map/flatMap映射 把 Stream中 的每一个元素，映射成另外一个元素。
         */
        Stream<String> wordList = Stream.of("hello","world","tom");
//        List<String> output = wordList.map(String::toUpperCase).collect(Collectors.toList());
        wordList.map(String::toUpperCase).collect(Collectors.toList()).forEach(System.out::println);

        List<Integer> nums = Arrays.asList(1,2,3,4);
        List<Integer> squareNums = nums.stream().map(n -> n*n).collect(Collectors.toList());
        squareNums.forEach(System.out::println);


        /**
         * map生成的是个1:1映射，每个输入元素，都按照规则转换成为另外一个元素。还有一些场景，是一对多映射关系的，这时需要 flatMap。
         * map和flatMap的方法声明是不一样的
         * <R> Stream<R>      map(Function<? super T, ? extends R> mapper);
         * <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
         */

        //stream1中的每个元素都是一个List集合对象
        Stream<List<Integer>> stream5 = Stream.of(
                Arrays.asList(1),
                Arrays.asList(2,3),
                Arrays.asList(4,5,6)
        );
        Stream<Integer> stream6 = stream5.flatMap((e) -> e.stream());
        //flatMap 把 stream5 中的层级结构扁平化，就是将最底层元素抽出来放到一起，最终新的 stream6 里面已经没有 List 了，都是直接的数字。
        stream6.forEach(System.out::println);

        //flatMap方法把stream7中的每一个字符串都用[.]分割成了俩个字符串
        //最后返回了一个包含4个字符串的stream2
        Stream<String> stream7 = Stream.of("tom.Li","lucy.Liu");
        Stream<String> stream8 = stream7.flatMap(s->Stream.of(s.split("[.]")));
        stream8.forEach(System.out::println);

        //forEach 遍历 接收一个 Lambda 表达式，然后在 Stream 的每一个元素上执行该表达式。
        //forEach 是 terminal 操作，执行完stream就不能再用了
        List<String> list1 = Arrays.asList("test","hello","world","java","tom","C","javascript");
        list1.stream().forEach(System.out::println);

        /**
         * filter 过滤 对原始 Stream 进行某项测试，通过测试的元素被留下来生成一个新 Stream。
         * 通过一个predicate接口来过滤并只保留符合条件的元素，该操作属于中间操作，所以我们可以在过滤后的结果来应用其他Stream操作（比如forEach）。
         * forEach需要一个函数来对过滤后的元素依次执行。forEach是一个最终操作，所以我们不能在forEach之后来执行其他Stream操作
         */
        List<String> list2 = Arrays.asList("test","hello","world","java","tom","C","javascript");
        list2.stream().filter(s -> s.length()>4).forEach(System.out::println); //注意:System.out::println 这个是lambda表达式中对静态方法的引用

        /**
         * peek 对每个元素执行操作并返回一个新的 Stream
         * 注意:调用peek之后,一定要有一个最终操作
         * peek是一个intermediate 操作
         */
        List<String> list3 = Arrays.asList("one", "two", "three", "four");
        List<String> list4 = list3.stream().filter(e -> e.length() > 3)
                .peek(e -> System.out.println("第一次符合条件的值为：" + e))
                .filter(e -> e.length()>4)
                .peek(e -> System.out.println("第二次符合条件的值为：" + e))
                .collect(Collectors.toList());
        System.out.println(list4.size());

        /**
         * findFirst 总是返回 Stream 的第一个元素，或者空，返回值类型：Optional。
         * 如果集中什么都没有,那么list.stream().findFirst()返回一个Optional<String>对象,但是里面封装的是一个null。
         */
        List<String> list5 = Arrays.asList("test","hello","world");
        Optional<String> first = list5.stream().findFirst();
        System.out.println(first.orElse("值为null"));

        /**
         * sort 排序
         * 排序是一个中间操作，返回的是排序好后的Stream。如果你不指定一个自定义的Comparator则会使用默认排序。
         * 对 Stream 的排序通过 sorted 进行，它比数组的排序更强之处在于你可以首先对 Stream 进行各类 map、filter、limit、skip 甚至 distinct
         * 来减少元素数量后，再排序，这能帮助程序明显缩短执行时间。
         */

        List<String> list6 = Arrays.asList("test","hello","world","java","tom","C","javascript");
        list6.stream().sorted().filter(s -> s.startsWith("j")).forEach(System.out::println);

        //按照字符串的长短排序
        //需要注意的是，排序只创建了一个排列好后的Stream，而不会影响原有的数据源，排序之后原数据list是不会被修改的
        list6.stream().sorted((s1,s2) -> s1.length()-s2.length()).forEach(System.out::println);

        /**
         * map 映射
         * 中间操作map会将元素根据指定的Function接口来依次将元素转成另外的对象，下面的示例展示了将字符串转换为大写字符串。
         * 你也可以通过map来讲对象转换成其他类型，map返回的Stream类型是根据你map传递进去的函数的返回值决定的。
         */
        List<String> list7 = Arrays.asList("test","hello","world","java","tom","C","javascript");
        list7.stream().map(s -> s.toUpperCase()).forEach(System.out::println);

        /**
         * Match 匹配
         * Stream提供了多种匹配操作，允许检测指定的Predicate是否匹配整个Stream。所有的匹配操作都是最终操作，并返回一个boolean类型的值。
         */
        //所有元素匹配成功才返回true 否则返回false
        List<String> list8 = Arrays.asList("test","hello","world","java","tom","C","javascript");
        boolean allMatch = list8.stream().allMatch((s)->s.startsWith("j"));
        System.out.println(allMatch);


        //任意一个匹配成功就返回true 否则返回false
        boolean anyMatch = list8.stream().anyMatch((s)->s.startsWith("j"));
        System.out.println(anyMatch);

        //没有一个匹配的就返回true 否则返回false
        boolean noneMatch = list8.stream().noneMatch((s)->s.startsWith("j"));
        System.out.println(noneMatch);

        /**
         * Count 计数
         * 计数是一个最终操作，返回Stream中元素的个数，返回值类型是long。
         */
        long count = list8.stream().filter(s->s.startsWith("j")).count();
        System.out.println(count);

        /**
         * Reduce 规约/合并
         * 这是一个最终操作，允许通过指定的函数来将stream中的多个元素规约合并为一个元素.它提供一个起始值（种子），然后依照运算规则（BinaryOperator），
         * 和前面 Stream 的第一个、第二个、第 n 个元素组合。Stream.reduce，常用的方法有average, sum, min, max, and count，返回单个的结果值，
         * 并且reduce操作每处理一个元素总是创建一个新值.
         * 从这个意义上说，字符串拼接、数值的 sum、min、max等都是特殊的 reduce。
         */
        IntStream integers = IntStream.range(1,10);
        Integer sum = integers.reduce(0,(a,b) -> a + b);
        //也可以这样
        //Integer sum1 = integers.reduce(0,Integer::sum);
        //也有没有起始值的情况，这时会把 Stream 的前面两个元素组合起来，返回的是 Optional
        //OptionalInt min = integers.reduce((a,b) -> a>b?a:b);
        // 字符串连接，concat = "ABCD"
        String concat = Stream.of("A", "B", "C", "D").reduce("",String::concat);
        System.out.println(concat);
        Optional<String> opStr = Stream.of("A", "B", "C", "D").reduce(String::concat);
        System.out.println(opStr.get());

        /**
         *     1.先调用stream方法
         *     2.再排序，按照字符串的长度进行排序，长的在前短的再后
         *     3.再过滤，字符串必须是以字符'j'开头的
         *     4.再进行映射，把每个字符串后面拼接上"_briup"
         *     5.再调用reduce进行合并数据,使用"|"连接字符串
         *     6.最后返回Optional<String>类型数据，处理好的字符串数据就封装在这个对象中
         */
        List<String> list9 =Arrays.asList("test","javap","hello","world","java","tom","C","javascript");
        Optional<String> optStr1 = list9.stream()
                .sorted((s1,s2) -> s2.length() - s1.length())
                .filter(s -> s.startsWith("j"))
                .map(s -> s + "_briup")
                .reduce((s1,s2)->s1+"|"+s2);
        System.out.println(optStr1.orElse("值为空"));

        /**
         * limit/skip
         * limit 返回 Stream 的前面 n 个元素；skip 则是跳过前 n 个元素只要后面的元素
         */
        List<String> list10 = Arrays.asList("test","javap","hello","world","java","tom","C","javascript");
        list10.stream().limit(5).forEach(System.out::println);
        list10.stream().skip(5).forEach(System.out::println);

        //min/max/distinct

        //找出字符文件中字符字符最长的一行
        /**
         * 注意:lines方法把文件中所有行都返回并且转换为一个Stream<String>类型对象,因为每行读出的String类型数据,
         * 同时String::length是使用方法引用的特殊方式(因为泛型的缘故),上面的笔记中已经介绍过了,max()方法执行后返回的时候OptionalInt类型对象,
         * 所以接着调用了getAsInt方法来获得这次运行结果的int值
         */
        BufferedReader br = new BufferedReader(new FileReader("D://a.txt"));
        int maxLen = br.lines().
                mapToInt(String::length).
                max().
                getAsInt();

        System.out.println(maxLen);

        //找出全文的单词，转小写，去掉空字符,去除重复单词并排序
        BufferedReader br1 = new BufferedReader(new FileReader("D://b.txt"));
        br1.lines().
                flatMap(s->Stream.of(s.split(" "))).
                filter(s->s.length()>0).
                map(s->s.toLowerCase()).
                distinct().
                sorted().
                forEach(System.out::println);

        /**
         * Stream.generate
         * 通过Supplier接口，可以自己来控制Stream的生成。这种情形通常用于随机数、常量的 Stream，或者需要前后元素间维持着某种状态信息的 Stream。
         * 把 Supplier 实例传递给 Stream.generate() 生成的 Stream，由于它是无限的，在管道中，必须利用limit之类的操作限制Stream大小。
         * 可以使用此方式制造出海量的测试数据
         * public static<T> Stream<T> generate(Supplier<T> s);
         * 生成100个随机数并由此创建出Stream实例
         */
        Stream.generate(()->(int)(Math.random()*100)).limit(100).forEach(System.out::println);

        /**
         * Stream.iterate
         * iterate 跟 reduce 操作很像，接受一个种子值，和一个 UnaryOperator（假设是 f）。
         * 然后种子值成为 Stream 的第一个元素，f(seed) 为第二个，f(f(seed)) 第三个，
         * f(f(f(seed))) 第四个,以此类推。
         * 该方法的声明为:
         * public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
         *
         * UnaryOperator接口继承了Function接口:
         * public interface UnaryOperator<T> extends Function<T, T>
         */
        //生成一个等差数列
        Stream.iterate(0, n -> n + 3).
                limit(10).
                forEach(x -> System.out.print(x + " "));
        /**
         * Collectors
         * java.util.stream.Collectors 类的主要作用就是辅助进行各类有用的操作。
         * 例如把Stream转变输出为 Collection，或者把 Stream 元素进行分组。
         */
        //把Stream中的元素进行过滤然后再转为List集合
        List<String> list12 = Arrays.asList("test","hello","world","java","tom","C","javascript");
        List<String> result = list12.stream().filter(s->s.length()>4).collect(Collectors.toList());

        //分组:按照字符串的长度分组
        //相同长度的字符串放到一个List集合中作为Map的value,字符串的长度作为Map的Key
        Map<Integer, List<String>> collect = list12.stream().collect(Collectors.groupingBy(String::length));
        //注意下面写法可能写到s->s.length()的时候Eclipse里面有可能不会代码提示，这个要看你先的是=号的哪一边
        //最终原因还是泛型的事情
        Map<Integer, List<String>> collect1 = list12.stream().collect(Collectors.groupingBy(s->s.length()));

        //分割:按照字符串是否包含java进行划分  partitioning分割划分的意思
        Map<Boolean, List<String>> collect2 =
                list12.stream().collect(Collectors.partitioningBy(s->s.indexOf("java")!=-1));
        for(Boolean b:collect2.keySet()){
            System.out.println(b+" : "+collect2.get(b).size());
        }
    }



}
